using System;
using System.Collections.Generic;
using CI.Interfaces;
using CI.Organism;
using System.Threading;
using System.Runtime.Serialization;
using CI.Utils;

namespace CI.Mesopotamia
{
    /// <summary>
    /// Implementation of the hosting environment using Microsoft Robotics Libaray
    /// </summary>
    [DataContract(IsReference = true)]
    [KnownType("GetKnownTypes")]
    public class MSRBHost : IHostingEnvironment
    {
        #region Properties

        /// <summary>Start time</summary>
        public DateTime StartTime
        {
            get { return _startTime; }
        }
        private DateTime _startTime;

        /// <summary>Organism Collection</summary>
        [DataMember]
        public IOrganismCollection Organisms
        {
            get
            {
                return _organisms;
            }
            set
            {
                _organisms = value;
            }
        }
        private IOrganismCollection _organisms = new OrganismCollection();

        /// <summary>Minimum time per heartbeat for all Organisms</summary>
        public TimeSpan HBOrganismCollectionMin { get; set; }

        /// <summary>
        /// Version of Hosting Environment
        /// </summary>
        public string VersionInformation
        {
            get
            {
                return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
            }
        }

        /// <summary>Heartbeat times to work off</summary>
        private DateTime _loopTime;

        /// <summary>Physical aspects of the hosting environment</summary>
        public IHostingEnvironmentPhysical HostingEnvironmentPhysical { get; set; }

        /// <summary>Simulation</summary>
        public ISimulationRestrictions Simulation { get; set; }

        /// <summary>Fitness function</summary>
        public IFitnessFunction FitnessFunction { get; set; }

        // curent time used in the sleep time calcs
        private DateTime _currentTime;

        /// <summary>System lag time of computing the entites, difference between acceptable time and actual</summary>
        public TimeSpan LagTime { get; set; }

        /// <summary>System Heart Beat counter</summary>
        public uint SystemHeartBeat { get; set; }

        /// <summary>
        /// Environment scenario
        /// </summary>
        public IScenario EnvironmentScenario { get; set; }

        /// <summary>
        /// Processing state eg, working, pausing
        /// </summary>
        public string ProcessingState
        {
            get { return _processingState; }
            set
            {
                if (_processingState != value)
                {
                    _processingState = value;
                }
            }
        }
        private string _processingState;

        /// <summary>alive list</summary>
        [DataMember]
        private List<IOrganism> _aliveList = new List<IOrganism>();

        // how long to sleep before we check pause request again if its true
        [DataMember]
        private int _pauseRequestSleep = 1000;

        /// <summary>
        /// Simulation environment
        /// </summary>
        private SimEnv _simEnv;
        #endregion

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="hostingEnvironmentPhysical">Physical hosting environment, eg visual simulator</param>
        /// <param name="environmentScenario">Environment scenario</param>
        public MSRBHost(IHostingEnvironmentPhysical hostingEnvironmentPhysical, IScenario environmentScenario)
        {

            #region Argument Validators
            if (hostingEnvironmentPhysical == null)
                throw new ArgumentException("hostingEnvironmentPhysical");
            if (environmentScenario == null)
                throw new ArgumentException("environmentScenario");
            #endregion

            EnvironmentScenario = environmentScenario;
            HostingEnvironmentPhysical = hostingEnvironmentPhysical;
            _organisms.hostEnvironment = this;
            _simEnv = SimEnv.Instance;
            _simEnv.HostEnv = this;
            ProcessingState = "Starting";
            _simEnv.TraceLogger.LogDebug("StartupMode : " + Globals.LaunchedFrom);
        }

        /// <summary>
        /// Main processing loop for the organims
        /// </summary>        
        public void Start()
        {
            #region Argument Validators

            if (Organisms == null)
                throw new ArgumentException("Organisms");

            if (Organisms.Alive == null)
                throw new ArgumentException("Organisms.Alive");

            if (Organisms.Dead == null)
                throw new ArgumentException("Organisms.Dead");

            #endregion

            // tell it what time it is
            _startTime = DateTime.Now;

            // clear all the dirty locations that may have been loaded
            Organisms.DirtyLocations = new List<IOrganism>();

            // check and make sure sim env is set, this can happen when desearlizing from xml
            if (_simEnv == null)
                _simEnv = SimEnv.Instance;

            _simEnv.MainLoopStart();

            // just check and make sure that we are in sync
            if (_simEnv.HostEnv != this)
                throw new Exception("Sim Env and MSRBHost are not the same!");

            // check if pause request from last run
            if (!_simEnv.BreakExit)
            {
                ProcessingState = "Startup paused:" + ProcessingState;
                CheckPauseRequest();
            }

            #region Main Loop
            // loop through alive organisms while breakexit is not set
            while ((!_simEnv.BreakExit) && (Organisms.Alive.Count > 0))
            {
                // increase the system heartbeat counters
                SystemHeartBeat++;

                LogMainLoopDiagnostics();

                // update status
                ProcessingState = "Running";

                // reset the counters
                _loopTime = DateTime.Now;

                // todo : parallelise this
                // fire the heart beat/neural system etc off for each org
                _aliveList.Clear();
                _aliveList.AddRange(Organisms.Alive);
                //Parallel.ForEach(_aliveList, (IOrganism organism, ParallelState psDirty) =>
                foreach (IOrganism organism in _aliveList)
                {
                    // lock it so no one else can touch it
                    if (Monitor.TryEnter(organism, Globals.CriticalAcquireLockTimeout))
                    {
                        try
                        {
                            // make sure we are still alive
                            if (Organisms.Alive.Contains(organism))
                            {
                                // fire the heartbeat
                                organism.Heartbeat();

                                // make sure it exists in the environment
                                if (organism.Born)
                                {
                                    // evaluate the neural system
                                    // it is possible for the heartbeat to kill the Organism and make it dead
                                    // if it runs out of energy so check first to make sure its still alive
                                    // also this is something i evently see taking a long time and hence farm it out to other machines
                                    if (Organisms.Alive.Contains(organism))
                                        if (organism.NeuralSystem != null)
                                            organism.NeuralSystem.Evaluate();
                                }

                                // check again for break mid cycle of alive organisms
                                if (_simEnv.BreakExit)
                                {
                                    break;
                                    //psDirty.Stop();
                                }

                            }
                        }
                        finally
                        {
                            Monitor.Exit(organism);
                        }
                    }
                    else
                    {
                        throw new MSRBHostException("Unable to acquire lock on organism");
                    }
                }//);

                // apply the FITNESS FUNCTION
                if ((FitnessFunction != null) && (!_simEnv.BreakExit))
                    FitnessFunction.UpdateOrganisms(Organisms);

                // if their are any restrictions applied to this simulation then apply them
                if ((Simulation != null) && (!_simEnv.BreakExit))
                    Simulation.RestrictionChecking(this);

                // wait for catch up
                if ((!_simEnv.BreakExit) && (HostingEnvironmentPhysical != null))
                    HostingEnvironmentPhysical.WaitForIdle();

                // run sanity check on the hosting environment, will check to make sure still in sync
                if ((!_simEnv.BreakExit) && (HostingEnvironmentPhysical != null))
                    HostingEnvironmentPhysical.SanityCheck();

                // check pause request
                if (!_simEnv.BreakExit)
                    CheckPauseRequest();

                // handle the lag time, pause if need be
                UpdateLagTimeAndPause();
            }

            _simEnv.MainLoopEnd();

            _aliveList.Clear();

            // let them know the state
            ProcessingState = "Stopping";

            #endregion

            // clean up all the orgs
            if (_simEnv.HostEnvPhysical != null)
                _simEnv.HostEnvPhysical.CleanUp();

            StatsLogger.TotalStats();

            // dump results to logs
            _simEnv.TraceLogger.LogDebug(Organisms.ToString(FitnessFunction));
        }

        private void LogMainLoopDiagnostics()
        {
            _simEnv.TraceLogger.LogDebug(string.Format("Scenario runtime : {0}", DateTime.Now.Subtract(_startTime)));
            _simEnv.TraceLogger.LogDebug(string.Format("System uptime : {0}", DateTime.Now.Subtract(_simEnv.StartTime)));
            _simEnv.TraceLogger.LogDebug(string.Format("Lag Time : {0}", LagTime));
            _simEnv.TraceLogger.LogDebug(string.Format("Break Exit : {0}", _simEnv.BreakExit));
            _simEnv.TraceLogger.LogDebug(string.Format("Organism Count:{0} Alive:{1} Dead:{2} Inactive:{3}", (_organisms.Alive.Count + _organisms.Dead.Count + _organisms.Inactive.Count), _organisms.Alive.Count, _organisms.Dead.Count, _organisms.Inactive.Count));
        }

        /// <summary>
        /// Loop while pause request is true
        /// </summary>
        private void CheckPauseRequest()
        {
            _simEnv.TraceLogger.LogDebug("Check Pause Request - Start");
            while ((_simEnv.PauseState) && (!_simEnv.BreakExit))
            {
                if (!ProcessingState.Contains("Paused"))
                    ProcessingState = "Paused - " + ProcessingState;
                _simEnv.TraceLogger.LogDebug(ProcessingState);
                StatsLogger.Sleeper(_pauseRequestSleep);
            }

            _simEnv.TraceLogger.LogDebug("Check Pause Request - End");
        }

        /// <summary>
        /// Caclulate the lag time
        /// </summary>
        private void UpdateLagTimeAndPause()
        {
            // if it hasnt been long enough yet for all the organisms
            // we are save the date time now as it is possible(and has happened) that before comparing and 
            // calculating the time changes and we get a negative number
            _currentTime = DateTime.Now;
            if (_loopTime.Add(HBOrganismCollectionMin).CompareTo(_currentTime) > 0)
            {
                // clear lag time
                LagTime = new TimeSpan();
                // sleep to run at min value
                StatsLogger.Sleeper(_loopTime.Add(HBOrganismCollectionMin).Subtract(_currentTime).Milliseconds);
            }
            else
            {
                // calc lag time
                LagTime = _currentTime.Add(HBOrganismCollectionMin).Subtract(_loopTime);
            }

        }

        /// <summary>
        /// Check and make sure the organism is within the bounds of the environment
        /// </summary>
        /// <param name="organism"></param>
        /// <returns>true if outside the bounds</returns>
        public bool BoundsChecking(IOrganism organism)
        {

            // if no max radius has been defined then break
            if ((organism.HostingEnvironment.EnvironmentScenario.BoundryXRadiusMax == 0) && (organism.HostingEnvironment.EnvironmentScenario.BoundryYRadiusMax == 0) && (organism.HostingEnvironment.EnvironmentScenario.BoundryZRadiusMax == 0))
            {

                _simEnv.TraceLogger.LogWarn("No hosting environment X,Y,Z radius defined");
                return false;
            }

            // get the location
            ILocation location = new Location(organism.Location.XAxis, organism.Location.YAxis, organism.Location.ZAxis);
            // make them all positive
            if (location.XAxis < 0)
                location.XAxis *= -1;
            if (location.YAxis < 0)
                location.YAxis *= -1;
            if (location.ZAxis < 0)
                location.ZAxis *= -1;

            // check xAxis
            if (location.XAxis > organism.HostingEnvironment.EnvironmentScenario.BoundryXRadiusMax)
            {
                organism.TerminateAndDestroy(string.Format("Outside Clipping Bounds XAxis, Allowed {0} Found {1} on organism {2}", organism.HostingEnvironment.EnvironmentScenario.BoundryXRadiusMax, location.XAxis, organism.Guid));
                return true;
            }

            // check yAxis
            if (location.YAxis > organism.HostingEnvironment.EnvironmentScenario.BoundryYRadiusMax)
            {
                organism.TerminateAndDestroy(string.Format("Outside Clipping Bounds YAxis, Allowed {0} Found {1} on organism {2}", organism.HostingEnvironment.EnvironmentScenario.BoundryYRadiusMax, location.YAxis, organism.Guid));
                return true;
            }

            // check zAxis
            if (location.ZAxis > organism.HostingEnvironment.EnvironmentScenario.BoundryZRadiusMax)
            {
                organism.TerminateAndDestroy(string.Format("Outside Clipping Bounds ZAxis, Allowed {0} Found {1} on organism {2}", organism.HostingEnvironment.EnvironmentScenario.BoundryZRadiusMax, location.ZAxis, organism.Guid));
                return true;
            }

            return false;
        }

        /// <summary>Called to register the cell in the environment, this may be different per environment</summary>
        public void RegisterPhysicalCell(ICell cell, ActionType actionType)
        {


            if (HostingEnvironmentPhysical != null)
            {
                if (actionType == ActionType.RemoveCell)
                    HostingEnvironmentPhysical.UnBindSynapseIO(cell.Organism);

                HostingEnvironmentPhysical.RegisterCell(cell, actionType);

                if (actionType == ActionType.AddCell)
                    HostingEnvironmentPhysical.BindSynapseIO(cell.Organism);
            }

            _simEnv.TraceLogger.LogDebug("Register Phyiscal cell/Organism done");
        }

        /// <summary>
        /// get the known types
        /// </summary>
        /// <returns></returns>
        internal static Type[] GetKnownTypes()
        {
            return Scenario.GetKnownTypes();
        }

        /// <summary>
        /// MSRBHost Exception
        /// </summary>
        private class MSRBHostException : Exception
        {
            private readonly SimEnv _simEnv = SimEnv.Instance;

            /// <summary>
            /// MSRBHost exception
            /// </summary>
            /// <param name="message"></param>
            public MSRBHostException(string message)
            {
                _simEnv.TraceLogger.LogWarn(message);
            }
        }
    }
}